/*
 *  World.cpp
 *  lab3
 *
 *  Created by Fredrik Gustafsson on 11/17/10.
 *  Copyright 2010 Kungliga Tekniska Högskolan. All rights reserved.
 *
 */

#include "World.h"

using namespace wok2;

#pragma mark Private
#pragma mark Menu funcions
bool World::go(){
	std::string direction;
	std::cin >> direction;
	Environment* new_environment = current_enviroment->neighbor(direction);
	if (new_environment != NULL) {
		if (new_environment->locked) {
			if (player->key_to_environment(new_environment)) {
				std::cout << "Using your key, you successfully opened the door!" << std::endl;
				new_environment->locked = false;
			} else {
				std::cout << "The door is locked!" << std::endl;
				return true;
			}
		}
		current_enviroment = new_environment;
		if (current_enviroment == goal_environment) {
			std::cout << "YOU REACHED THE GOAL! byebye" << std::endl;
			return false;
		}
	}
	else 
	{
		std::cout << "You can't go that way." << std::endl;
	}
	std::cout << *current_enviroment;
	return true;
}

bool World::take() {
	std::string item_name;
	std::cin >> item_name;
	Item* item = current_enviroment->pop_item(item_name);
	if (item != NULL) {
		player->push_item(item);
		std::cout << "Sucessfully picked up the " << item_name << std::endl;
	} else {
		std::cout << "There is no such item here!" << std::endl;
	}
	return true;
}

bool World::drop() {
	std::string item_name;
	std::cin >> item_name;
	Item* item = player->pop_item(item_name);
	if (item != NULL) {
		std::cout << "You dropped the " << item_name << std::endl;
		if (item->type().compare("jar")==0) {
			std::cout << "The Jar broke!" << std::endl;
			current_enviroment->push_item(((Jar*)item)->contains);
			delete item;
		} else {
			current_enviroment->push_item(item);
		}
	} else {
		std::cout << "You have no such item." << std::endl;
	}
	return true;
}

bool World::save() {
	char file_name[50];
	std::cin >> file_name;
	std::ofstream ofs(file_name);
	if(write_world(ofs)) {
		std::cout << "Succesfully saved the game to " << file_name << "." << std::endl;
	} else {
		std::cout << "Couldn't save the game to " << file_name << "." << std::endl;
	}

	ofs.close();
	return true;
}

bool World::eat() {
	std::string item_name;
	std::cin >> item_name;
	Item* item = player->pop_item(item_name);
	
	if (item==NULL){
		std::cout << "Nothing named " << item_name << " in your inventory." << std::endl;
		display_room();
		return true;
	}
	if (item->type().compare("berry")==0) {
#ifdef DEBUG_DEALLOC
		std::cout << "*DBG* Deallocating:" << item->name << std::endl;
#endif
		delete item;
		std::cout << "You ate the berry, gained life. You now have " <<player->health <<"hp" << std::endl;
		player->health++;
	} else {
		player->push_item(item);
		std::cout << "You can't eat that, weirdo..." << std::endl;
	}
	return true;
}

bool World::load() {
	char file_name[50];
	std::cin >> file_name;
	
	//Destruct the old world
	dealloc_world();
	
	std::ifstream ifs(file_name);
	if(read_world(ifs)) {
		std::cout << "Succesfully loaded the game from " << file_name << "." << std::endl;
		init_world();
	} else {
		ifs.close();
		std::cout << "Couldn't load the game from " << file_name << "." << std::endl;
		return false;
	}
	ifs.close();
	return true;
}

bool World::display_inventory() {
	player->print_inventory();
	return true;
}

bool World::display_room() {
	std::cout << *current_enviroment;
	return true;
}

bool World::fight(){
	std::string actor_name;
	std::cin >> actor_name;
	Actor* enemy = current_enviroment->pop_actor(actor_name);
	if (enemy == NULL) {
		std::cout << "there is nothing with that name to fight!" << std::endl;
		return true;
	}
	std::cout << "You are now in battle with "<<actor_name <<"!" << std::endl;
	//fight parser
	bool skip_round = (rand()%2 == 0);
	while (1) {
		if (!skip_round) {
			std::cout << "it is your turn to attack."<<std::endl;
			std::string command;
			std::cin >> command;
			if (command.compare("hit")==0) {
				std::cout << "You attack the " << enemy->type() << "!" << std::endl;
				int damage = player->gen_damage(20,10);
				enemy->health-= damage;
				usleep(1000000);
				std::cout << "You did " << damage << " damage to " << enemy->name << "." << std::endl;
				usleep(1000000);
				if (enemy->health<=0) {
					std::cout << "Your hit killed the enemy!" << std::endl << std::endl;
#ifdef DEBUG_DEALLOC
					std::cout << "*DBG* Deallocating:" << enemy->name << std::endl;
#endif
					delete(enemy);
					usleep(1000000);
					std::cout << *current_enviroment;
					return true;
				}
			} 
			else if ((player->peek_item("sword") != NULL ) && command.compare("fence")==0){
				std::cout << "EN GARDE, "<< enemy->name << "!" << std::endl;
				int damage = player->gen_damage(45,42);
				enemy->health-= damage;
				usleep(2500000);
				std::cout << "You did " << damage << " damage to " << enemy->name << "." << std::endl;
				usleep(1000000);
				if (enemy->health<=0) {
					std::cout << "Your hit killed the enemy!" << std::endl << std::endl;
#ifdef DEBUG_DEALLOC
					std::cout << "*DBG* Deallocating:" << enemy->name << std::endl;
#endif
					delete(enemy);
					usleep(1000000);
					std::cout << *current_enviroment;
					return true;
				}	
			}		
			
			else {
				std::cout << "You can not " << command <<" the ememy! valid commands are: hit";
				if (player->peek_item("sword") != NULL) {
					std::cout << ", fence";
				}
				std::cout << std::endl;
				usleep(1000000);
			}
			
		}
		skip_round=false;
		std::cout << "It is "<<actor_name << "s turn to attack."<<std::endl;
		enemy->fight(((*player)));
		if (player->health<=0){
			std::cout << "You have been killed by " << enemy->name <<"!" <<std::endl;
			usleep(1000000);
			std::cout << "GAME OVER :(" << std::endl;
			return false;
		}
		
	}
	return true;
}

bool World::quit() {
	return false;
}

bool World::talk() {
	std::string actor_name;
	std::cin >> actor_name;
	Actor* actor = current_enviroment->peek_actor(actor_name);
	if (actor != NULL) {
		if (actor->type().compare("human")==0) {
			((Human*)actor)->talk_to();
			usleep(1000000);
			display_room();
		} else {
			std::cout << "You can't talk to non-humans!" << std::endl;
		}
		
	} else {
		std::cout << "No such human." << std::endl;
	}
	return true;
}

#pragma mark -
#pragma mark Public

#pragma mark Constructor&Destructor

World::World() {
	
}
void World::dealloc_world() {
	std::map<std::string,Environment*>::iterator env_it;
	for (env_it = environments.begin(); env_it!=environments.end(); env_it++) {
		if (env_it->second != NULL) {
#ifdef DEBUG_DEALLOC
			std::cout << "*DBG* Deallocating:" << env_it->second->name << std::endl;
#endif
			delete env_it->second;
			env_it->second = NULL;
			environments.erase(env_it);
		}
	}
#ifdef DEBUG_DEALLOC
	std::cout << "*DBG* Deallocating:" << player->name << std::endl;
#endif
	delete player;
}

World::~World() {
	dealloc_world();
}

#pragma mark Command parser

void World::init_world(){
	//Initialize commands
	world_commands["drop"] = &World::drop;
	world_commands["go"] = &World::go;
	world_commands["inventory"] = &World::display_inventory;
	world_commands["room"] = &World::display_room;
	world_commands["take"] = &World::take;
	world_commands["eat"] = &World::eat;
	world_commands["quit"] = &World::quit;
	world_commands["fight"] = &World::fight;
	world_commands["talk"] = &World::talk;
	world_commands["save"] = &World::save;
	world_commands["load"] = &World::load;
	
	//Setup player
	player = new Human("You", "");
#ifdef DEBUG_ALLOC
	std::cout << "*DBG* Allocated:" << player->name << std::endl;
#endif
	
	//Print welcome message
	std::cout << welcome_message << std::endl << "Type help for help." << std::endl;
	usleep(1000000);
	
	//Print the room
	std::cout << *current_enviroment;
}

bool World::parseWorld(){
	std::string command;
	std::cin >> command;;
	std::map<std::string,Command >::const_iterator call = world_commands.find(command);
	if (call == world_commands.end()) {
		std::cout << "Unknown command." << std::endl << "Valid commands are: ";
		std::map<std::string,Command>::iterator it;
		for (it = world_commands.begin(); it!=world_commands.end(); it++) {
			if (it != world_commands.begin()) {
				std::cout << ", ";
			}
			std::cout << it->first;
		}
		std::cout << std::endl;
	}
	else {
		return (this->*(*call).second) ();
	}
	return true;
}

void World::runWorldParser(){
	init_world();
	
	//Do the parsing
	while (parseWorld()) {}
}


#pragma mark File parser

//Max linesize
//TODO: Think about lowering max linesize
#define BUFFER_SIZE 2048

bool World::read_world(std::istream& ifs) {
	
	char buffer[BUFFER_SIZE];
	std::map<std::string,Actor*> actors;
	std::map<std::string,Item*> items;
	
	do {
		if(!ifs.good()) return false;
		ifs.getline(buffer, BUFFER_SIZE);
	} while (strcmp(buffer, "@world")!=0);
	
	while (1) {
		if(!ifs.good()) return false;
		ifs.getline(buffer, BUFFER_SIZE);
		
		if(buffer[0]=='#') {
			continue;
		} else if(strcmp(buffer, "@inside")==0) {
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);
			
			char locked[BUFFER_SIZE];
			ifs.getline(locked, BUFFER_SIZE);
			
			char description[BUFFER_SIZE];
			ifs.getline(description, BUFFER_SIZE);
			
			Inside* inside = new Inside(name, description);
#ifdef DEBUG_ALLOC
			std::cout << "*DBG* Allocated:" << name << std::endl;
#endif
			environments[name] = inside;
			if(locked[0]=='l') inside->locked = true;
			
		} else if(strcmp(buffer, "@outside")==0) {
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);
			
			char locked[BUFFER_SIZE];
			ifs.getline(locked, BUFFER_SIZE);
			
			char description[BUFFER_SIZE];
			ifs.getline(description, BUFFER_SIZE);
			
			Outside* outside = new Outside(name, description);
#ifdef DEBUG_ALLOC
			std::cout << "*DBG* Allocated:" << name << std::endl;
#endif
			environments[name] = outside;
			if(locked[0]=='l') outside->locked = true;
			
		} else if(strcmp(buffer, "@forest")==0) {
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);
			
			char locked[BUFFER_SIZE];
			ifs.getline(locked, BUFFER_SIZE);
			
			char description[BUFFER_SIZE];
			ifs.getline(description, BUFFER_SIZE);
			
			Forest* outside = new Forest(name, description);
#ifdef DEBUG_ALLOC
			std::cout << "*DBG* Allocated:" << name << std::endl;
#endif
			environments[name] = outside;
			if(locked[0]=='l') outside->locked = true;
			
		} else if(strcmp(buffer, "@goal")==0) {
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);
			
			goal_environment = environments[name];
			
		} else if(strcmp(buffer, "@start")==0) {			
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);

			current_enviroment = environments[name];
			
		} else if(strcmp(buffer, "@join")==0) {
			char env1_name[BUFFER_SIZE];
			ifs.getline(env1_name, BUFFER_SIZE);
			
			char env1_direction[BUFFER_SIZE];
			ifs.getline(env1_direction, BUFFER_SIZE);
			
			char env2_name[BUFFER_SIZE];
			ifs.getline(env2_name, BUFFER_SIZE);
			
			environments[env1_name]->add_neighbor(env1_direction, *environments[env2_name]);
		} else if(strcmp(buffer, "@item")==0) {
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);
			
			char description[BUFFER_SIZE];
			ifs.getline(description, BUFFER_SIZE);
			
			Item* item = new Item(name, description);
#ifdef DEBUG_ALLOC
			std::cout << "*DBG* Allocated:" << name << std::endl;
#endif
			items[name] = item;
		} else if(strcmp(buffer, "@key")==0) {
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);
			
			char description[BUFFER_SIZE];
			ifs.getline(description, BUFFER_SIZE);
			
			char env[BUFFER_SIZE];
			ifs.getline(env, BUFFER_SIZE);
			
			Item* item = new Key(name, description, environments[env]);
#ifdef DEBUG_ALLOC
			std::cout << "*DBG* Allocated:" << name << std::endl;
#endif
			items[name] = item;
		} else if(strcmp(buffer, "@jar")==0) {
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);
			
			char description[BUFFER_SIZE];
			ifs.getline(description, BUFFER_SIZE);
			
			char to_contain[BUFFER_SIZE];
			ifs.getline(to_contain, BUFFER_SIZE);
			
			Item* item = new Jar(name, description, items[to_contain]);
#ifdef DEBUG_ALLOC
			std::cout << "*DBG* Allocated:" << name << std::endl;
#endif
			items[name] = item;
		} else if(strcmp(buffer, "@place")==0) {
			char env[BUFFER_SIZE];
			ifs.getline(env, BUFFER_SIZE);
			
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);

			environments[env]->push_item(items[name]);			
		} else if(strcmp(buffer, "@monster")==0) {
			char env[BUFFER_SIZE];
			ifs.getline(env, BUFFER_SIZE);
			
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);
			
			Actor* actor = new Monster(name);
#ifdef DEBUG_ALLOC
			std::cout << "*DBG* Allocated:" << name << std::endl;
#endif
			actors[name] = actor;
			environments[env]->push_actor(actor);
			
		} else if(strcmp(buffer, "@human")==0) {
			char env[BUFFER_SIZE];
			ifs.getline(env, BUFFER_SIZE);
			
			char name[BUFFER_SIZE];
			ifs.getline(name, BUFFER_SIZE);
			
			char to_say[BUFFER_SIZE];
			ifs.getline(to_say, BUFFER_SIZE);
			
			Actor* actor = new Human(name, to_say);
#ifdef DEBUG_ALLOC
			std::cout << "*DBG* Allocated:" << name << std::endl;
#endif
			actors[name] = actor;
			environments[env]->push_actor(actor);
		} else if(strcmp(buffer, "@welcome")==0) {
			char message[BUFFER_SIZE];
			ifs.getline(message, BUFFER_SIZE);
			
			welcome_message = message;
		} else if(strcmp(buffer, "@end")==0) {
			break;
		}
	}
	
	return true;
}

std::istream& operator>>(std::istream& is, World& w) {
	w.read_world(is);
	return is;
}

bool World::write_world(std::ostream& os) {
	std::map<std::string,Environment*>::iterator env_it;
	os << "@world" << std::endl
	<< "#Saved world, generated by LAB3" << std::endl << std::endl
	<< "@welcome" << std::endl
	<< welcome_message << std::endl << std::endl;
	
	//Save environments
	for (env_it = environments.begin(); env_it!=environments.end(); env_it++) {
		Environment* env = env_it->second;
		if (env != NULL) {
			os << "@" << env->type() << std::endl
			<< env->name << std::endl
			<< (env->locked ? "locked" : "unlocked") << std::endl
			<< env->description << std::endl << std::endl;
		}
	}
	
	//Save doors
	for (env_it = environments.begin(); env_it!=environments.end(); env_it++) {
		Environment* env = env_it->second;
		if (env != NULL) {
			env->write_neighbors(os);
		}
	}
	
	//Save items
	for (env_it = environments.begin(); env_it!=environments.end(); env_it++) {
		Environment* env = env_it->second;
		if (env != NULL) {
			env->write_items(os);
		}
	}
	
	//Save actors
	for (env_it = environments.begin(); env_it!=environments.end(); env_it++) {
		Environment* env = env_it->second;
		if (env != NULL) {
			env->write_actors(os);
		}
	}
	
	//Save start
	os << "@start" << std::endl
	<< current_enviroment->name << std::endl << std::endl;
	
	//Save goal
	os << "@goal" << std::endl
	<< goal_environment->name << std::endl << std::endl;
	
	//End
	os << "@end" << std::endl;
	return true;
}

std::ostream& operator<<(std::ostream& os, World& w) {
	w.write_world(os);
	return os;
}
